#include "core/main.h"

namespace PongConstants
{
    static const int blockHeight = 8;
    static const int blockWidth = 16;

    static const int ballRadius = 4;

    static const int paddleHeight = 8;
    static const int paddleWidth = 32;
    static const int paddleSpeed = 10;

    static const int numBlockRows = 0;
    static const int numBlockCols = 0;

    // Size of the area that the paddle and ball can occupy, in pixels
    static const int gameAreaHeight = 220;
    static const int gameAreaWidth  = 130;

    // Height of the area (in pixels) between the top of the blocks and the top
    // of the game area.
    static const int topAreaHeight = 32;

    static const int blueBlockScore = 10;
    static const int paddleBounceScore = 200;
    static const int deathPenalty = 1000;

    static const int maxBouncesPerEpisode = 50;

    static const bool drawWalls = false;
};

void GamePong::Init()
{
    _emptyBlock = -1;
    _spritePaddle1   = _collection.AddSprite(Bitmap::LoadFile("../assets/pong/paddle1Rotated.png"), "paddle1Rotated");
    _spritePaddle2   = _collection.AddSprite(Bitmap::LoadFile("../assets/pong/paddle2Rotated.png"), "paddle2Rotated");
    _spriteBallUp    = _collection.AddSprite(Bitmap::LoadFile("../assets/pong/ballUp.png"),    "ballUp");
    _spriteBallDown  = _collection.AddSprite(Bitmap::LoadFile("../assets/pong/ballDown.png"),  "ballDown");
    _spriteWall      = _collection.AddSprite(Bitmap::LoadFile("../assets/pong/wall.png"),      "wall");
    _spriteBlueBlock = _collection.AddSprite(Bitmap::LoadFile("../assets/pong/blueBlock.png"), "blueBlock");

    _collection.AddFontSprites();

    _observedIntNames.push_back("Reset");
    _observedIntNames.push_back("Score");
    _observedIntNames.push_back("Frame Score");
    _observedIntNames.push_back("Level");
    _observedIntNames.push_back("dummy");
    _observedIntNames.push_back("Frames since reset");
    _observedIntNames.push_back("Bounces since reset");

    _activeFrame.observedInts.resize(ObservedIntNames().size());
}

void GamePong::NewGame()
{
    _state.level = 0;
    _state.score = 0;
    NextLevel();
}

void GamePong::Step(ControllerState &controller)
{
    int frameScore = 0;

    // Update paddle position
    if (controller.left()) _state.paddlePosition -= PongConstants::paddleSpeed;
    if (controller.right()) _state.paddlePosition += PongConstants::paddleSpeed;
    int paddleLow = PongConstants::paddleWidth / 2;
    int paddleHigh = PongConstants::gameAreaWidth - PongConstants::paddleWidth / 2;
    _state.paddlePosition = Math::Bound(_state.paddlePosition, paddleLow, paddleHigh);

    _state.ballXPosition += _state.ballXVelocity;
    _state.ballYPosition += _state.ballYVelocity;

    // Update paddle 2 position
    _state.paddleOpponentPosition = _state.ballXPosition;
    _state.paddleOpponentPosition = Math::Bound(_state.paddleOpponentPosition, paddleLow, paddleHigh);


    // Check for intersections between the ball and the side walls
    int ballRadius = PongConstants::ballRadius;
    if (_state.ballXPosition < ballRadius) {
        _state.ballXPosition = ballRadius;
        _state.ballXVelocity *= -1;
    } else if (_state.ballXPosition + ballRadius >= PongConstants::gameAreaWidth) {
        _state.ballXPosition = PongConstants::gameAreaWidth - ballRadius;
        _state.ballXVelocity *= -1;
    }

    // Check for intersection between the ball and the top wall
    if (_state.ballYPosition + ballRadius >= PongConstants::gameAreaHeight) {
        _state.ballYPosition = PongConstants::gameAreaHeight - ballRadius;
        _state.ballYVelocity *= -1;
    }

    bool reset = false;
    // Check for game over
    if (_state.ballYPosition < 0) {
        reset = true;
        _state.level = 0;
        frameScore -= PongConstants::deathPenalty;
    }

    if (_state.bouncesSinceReset == PongConstants::maxBouncesPerEpisode)
    {
        reset = true;
    }

    if (!reset) {

        // Check for intersection between the ball and the paddle
        int ballPaddleXDist = _state.ballXPosition - _state.paddlePosition;
        if (_state.ballYPosition - ballRadius <= PongConstants::paddleHeight
            && ballPaddleXDist >= - PongConstants::paddleWidth / 2
            && ballPaddleXDist <= PongConstants::paddleWidth / 2
            && _state.ballYVelocity < 0) {
            _state.ballXVelocity = GetBallXVelocity(ballPaddleXDist);
            _state.ballYVelocity *= -1;
            frameScore += PongConstants::paddleBounceScore;
            _state.bouncesSinceReset++;
        }

        // Check for intersections between ball and each block
        for (unsigned int y = 0; y < _state.blocks.Rows(); y++) {
            for (unsigned int x = 0; x < _state.blocks.Cols(); x++) {
                int blockId = _state.blocks(y, x);
                if (blockId != _emptyBlock) {
                    int blockXMin = x * PongConstants::blockWidth;
                    int blockXMax = blockXMin + PongConstants::blockWidth;
                    int blockYMax = PongConstants::gameAreaHeight - PongConstants::topAreaHeight;
                    blockYMax -= y * PongConstants::blockHeight;
                    int blockYMin = blockYMax - PongConstants::blockHeight;

                    int ballRadius = PongConstants::ballRadius;
                    if (_state.ballYPosition + ballRadius >= blockYMin
                        && _state.ballYPosition - ballRadius <= blockYMax
                        && _state.ballXPosition + ballRadius >= blockXMin
                        && _state.ballXPosition - ballRadius <= blockXMax) {

                            // This logic is a bit screwy right now
                            if (_state.ballYPosition < blockYMin + PongConstants::blockHeight / 4
                                || _state.ballYPosition > blockYMax - PongConstants::blockHeight / 4) {
                                    _state.ballYVelocity *= -1;
                            }

                            if (_state.ballXPosition < blockXMin + PongConstants::blockWidth / 2
                                || _state.ballXPosition > blockXMax + PongConstants::blockWidth / 2) {
                                    _state.ballXVelocity *= -1;
                            }

                            _state.blocks(y, x) = _emptyBlock;
                            _state.blocksRemaining--;
                            frameScore += PongConstants::blueBlockScore;
                    }
                }
            }
        }

        _state.score += frameScore;

        if (_state.blocksRemaining == 0) {
            reset = true;
        }
    }

    _state.framesSinceReset++;

    _activeFrame.observedInts[0] = reset ? 1 : 0;
    _activeFrame.observedInts[1] = _state.score;
    _activeFrame.observedInts[2] = frameScore;
    _activeFrame.observedInts[3] = _state.level;
    _activeFrame.observedInts[4] = 0;
    _activeFrame.observedInts[5] = _state.framesSinceReset;
    _activeFrame.observedInts[6] = _state.bouncesSinceReset;

    if (reset) {
        NextLevel();
    }

    MakeFrameSprites();
}

int GamePong::GetBallXVelocity(int offset) {
    int sign = offset > 0 ? 1 : -1;
    int mag = offset * sign;

    int paddleWidth = PongConstants::paddleWidth;
    int velocity = 0;
    if (mag > paddleWidth / 32 && mag <= paddleWidth / 4) velocity = 1;
    else if (mag > paddleWidth / 4 && mag <= paddleWidth / 2) velocity = 2;
    else if (mag > paddleWidth / 2) velocity = 3;

    return sign * velocity;
}

ControllerState GamePong::SimpleAI()
{
    ControllerState result;

    float target = (PongConstants::paddleWidth / 2 - 3) * pmrnd();
    if (_state.ballXPosition > _state.paddlePosition - target) {
        result.KeyDown('d');
    } else {
        result.KeyDown('a');
    }

    return result;
}

void GamePong::NextLevel()
{
    _state.paddlePosition = PongConstants::gameAreaWidth / 2;

    _state.ballXPosition = PongConstants::gameAreaWidth / 2;
    _state.ballYPosition = PongConstants::paddleHeight + PongConstants::ballRadius;

    _state.ballXVelocity = 1;
    _state.ballYVelocity = 1;

    _state.level++;
    _state.blocks.Allocate(PongConstants::numBlockRows, PongConstants::numBlockCols, _spriteBlueBlock);
    _state.blocksRemaining = PongConstants::numBlockRows * PongConstants::numBlockCols;
    
    // Check for the case that we are actually playing wall-ball
    if (_state.blocksRemaining == 0) {
        _state.blocksRemaining = 1;
    }

    _state.framesSinceReset = 0;
    _state.bouncesSinceReset = 0;
}

void GamePong::MakeFrameSprites()
{
    _activeFrame.sprites.clear();

    // Draw the walls
    if (PongConstants::drawWalls) {
        int wallHeight = PongConstants::gameAreaHeight / Constants::NESTileSize;
        for (int y = 1; y <= wallHeight; y++) {
            int screenY = Constants::NESHeight - 8 * y;
            _activeFrame.sprites.push_back(SpriteLocation(Vec2i(0, screenY), _spriteWall, 0));
            _activeFrame.sprites.push_back(SpriteLocation(Vec2i(Constants::NESWidth - 8, screenY), _spriteWall, 0));
        }
        for (int x = 0; x < Constants::NESGridWidth; x++) {
            int screenY = Constants::NESHeight - 8 * (wallHeight + 1);
            int screenX = 8 * x;
            _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenX, screenY), _spriteWall, 0));
        }
    }

    // Draw the paddle
    int paddleScreenX = 8 + _state.paddlePosition - PongConstants::paddleWidth / 2;
    int paddleScreenY = Constants::NESHeight - PongConstants::paddleHeight;
    _activeFrame.sprites.push_back(SpriteLocation(Vec2i(Constants::NESHeight-paddleScreenY, paddleScreenX), _spritePaddle1, 0));

    // Draw the paddle
    int paddleOpponentScreenX = 8 + _state.paddleOpponentPosition - PongConstants::paddleWidth / 2;
    int paddleOpponentScreenY = Constants::NESHeight - PongConstants::gameAreaHeight - PongConstants::paddleHeight;
    _activeFrame.sprites.push_back(SpriteLocation(Vec2i(Constants::NESHeight-paddleOpponentScreenY, paddleOpponentScreenX), _spritePaddle2, 0));

    if (_state.ballYVelocity > 0)
    {
        // Draw the ball
        int ballScreenX = 8 + _state.ballXPosition - PongConstants::ballRadius;
        int ballScreenY = Constants::NESHeight - _state.ballYPosition - PongConstants::ballRadius;
        _activeFrame.sprites.push_back(SpriteLocation(Vec2i(Constants::NESHeight-ballScreenY, ballScreenX), _spriteBallUp, 1));
    }
    else
    {
        // Draw the ball
        int ballScreenX = 8 + _state.ballXPosition - PongConstants::ballRadius;
        int ballScreenY = Constants::NESHeight - _state.ballYPosition - PongConstants::ballRadius;
        _activeFrame.sprites.push_back(SpriteLocation(Vec2i(Constants::NESHeight-ballScreenY, ballScreenX), _spriteBallDown, 1));
    }

    // Draw the blocks
    for (unsigned int y = 0; y < _state.blocks.Rows(); y++) {
        for (unsigned int x = 0; x < _state.blocks.Cols(); x++) {
            int screenX = 8 + x * PongConstants::blockWidth;
            int screenY = Constants::NESHeight - PongConstants::gameAreaHeight + PongConstants::topAreaHeight;
            screenY += y * PongConstants::blockHeight;
            int spriteId = _state.blocks(y, x);
            if (spriteId != _emptyBlock) {
                _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenX, screenY), spriteId, 0));
            }
        }
    }

    _collection.DrawText("SCORE", Vec2i(3, 1), _activeFrame.sprites, 0);
    _collection.DrawNumber(_state.score, 7, Vec2i(2, 2), _activeFrame.sprites, 0);

    _collection.DrawText("LEVEL", Vec2i(15, 1), _activeFrame.sprites, 0);
    _collection.DrawNumber(_state.level, 3, Vec2i(16, 2), _activeFrame.sprites, 0);
}